package com.jc.repository.generator;

/**
 * @author wangli
 * @create 2016-04-27 下午3:50
 * @description
 */


import com.google.common.base.Charsets;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Collections2;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.jc.base.Assert;
import com.jc.base.ComponentException;
import com.jc.repository.generator.utils.FreeMarkers;
import com.jc.repository.generator.utils.GenerateBaseDir;
import com.jc.repository.generator.utils.GenerateConfig;
import com.jc.repository.generator.utils.GenerateIsCover;
import com.jc.repository.generator.utils.GenerateMode;
import com.jc.repository.generator.utils.MapperInfo;
import com.jc.utils.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.api.ProgressCallback;
import org.mybatis.generator.api.VerboseProgressCallback;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.mybatis.generator.internal.util.messages.Messages.getString;


public class GenerateMybatis {

    private static final String GENERATOR_ING = "...正在生成...";
    private static final String GENERATOR_OK = "...成功生成...";
    private static final String GENERATOR_FILE_EXISTED = "...待生成的文件已经存在...";

    private static final Logger LOG = LoggerFactory.getLogger(GenerateMybatis.class);

    /**
     * @param config_dir 配置文件的根目录
     * @param XML_NAME   配置文件的文件名
     */
    public static void generator(String config_dir, String XML_NAME) {

        List<String> warnings = new ArrayList<String>();
        Set<String> fullyqualifiedTables = new HashSet<String>();

        Set<String> contexts = new HashSet<String>();
        try {
            for (File configurationFile : new File(config_dir).listFiles()) {
//                System.out.println(configurationFile.getName());
                if (configurationFile.getName().equals(XML_NAME)) {
                    ConfigurationParser cp = new ConfigurationParser(warnings);
                    Configuration config = cp.parseConfiguration(configurationFile);

                    DefaultShellCallback shellCallback = new DefaultShellCallback(Boolean.TRUE);

                    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, shellCallback, warnings);

                    ProgressCallback progressCallback = new VerboseProgressCallback();

                    myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);
                }
            }
        } catch (XMLParserException e) {
            writeLine(getString("Progress.3")); //$NON-NLS-1$
            writeLine();
            for (String error : e.getErrors()) {
                writeLine(error);
            }

            return;
        } catch (SQLException e) {
            e.printStackTrace();
            return;
        } catch (IOException e) {
            e.printStackTrace();
            return;
        } catch (InvalidConfigurationException e) {
            writeLine(getString("Progress.16")); //$NON-NLS-1$
            for (String error : e.getErrors()) {
                writeLine(error);
            }
            return;
        } catch (InterruptedException e) {
            // ignore (will never happen with the DefaultShellCallback)
            ;
        }

        for (String warning : warnings) {
            writeLine(warning);
        }

        if (warnings.size() == 0) {
            writeLine(getString("Progress.4")); //$NON-NLS-1$
        } else {
            writeLine();
            writeLine(getString("Progress.5")); //$NON-NLS-1$
        }
    }


    /**
     * 生成dao和service层的代码
     *
     * @param baseDir          基础目录
     * @param config           配置信息
     * @param blackOrWhiteList 黑名单或者白名单
     * @return
     */
    public static List<MapperInfo> generateDaoAndServiceCode(GenerateBaseDir baseDir, GenerateConfig config, final List<String> blackOrWhiteList) {
        try {


            /**
             * Step 1:先将mapper目录下面的子目录找出来
             */
            FluentIterable<File> iterable = Files.fileTreeTraverser().breadthFirstTraversal(new File(baseDir.getMapper())).filter(
                    new Predicate<File>() {
                        @Override
                        public boolean apply(File file) {
                            return
                                    file.isDirectory()
                                            &&
                                            !file.getAbsoluteFile().toString().endsWith("ex")
                                            &&
                                            !file.getAbsoluteFile().toString().endsWith("mapper");

                        }
                    });

            final List<MapperInfo> mapperInfos = Lists.newArrayList();

            /**
             * Step 2: 将mapper子目录下面的文件扫描出来
             */
            for (File f : iterable) {

//                System.out.println(f.getAbsolutePath());

                FluentIterable<File> mapperFileDetail =
                        Files.
                                fileTreeTraverser()
                                .breadthFirstTraversal(
                                        new File(f.getAbsolutePath())).filter(
                                new Predicate<File>() {

                                    /**
                                     * 使用迭代器, 去除ex 目录下的文件
                                     *
                                     * @param file
                                     * @return
                                     */
                                    @Override
                                    public boolean apply(File file) {
                                        return !file.getAbsoluteFile().toString().toLowerCase().contains("ex");
                                    }
                                });

                //获取每个类代码的生成信息
                /**
                 * Step 3: mapperjava文件的具体路径确定MapperInfo
                 */
                for (File mapperFile : mapperFileDetail) {
                    if (mapperFile.isFile()) {

                        MapperInfo mapperInfo = new MapperInfo(mapperFile, baseDir);
                        mapperInfos.add(mapperInfo);


                    }
                }

            }

            /**
             * Step 4: 扫描mapper根目录下面的java文件
             */
            Collection<File> rootFiles = Collections2.filter(Arrays.asList(new File(baseDir.getMapper()).listFiles()), new Predicate<File>() {
                @Override
                public boolean apply(File input) {
                    return input.isFile();
                }
            });

            for (Iterator<File> iterator = rootFiles.iterator(); iterator.hasNext(); ) {
                File rootFile = iterator.next();
                MapperInfo rootMapperInfo = new MapperInfo(rootFile, baseDir).moudleName(null);
                mapperInfos.add(rootMapperInfo);
            }


            List<MapperInfo> mapperInfosTobeGenerated = Collections.EMPTY_LIST;

            switch (config.getMode()) {
                case 全部生成: {
                    mapperInfosTobeGenerated = mapperInfos;
                    break;
                }
                case 黑名单: {
                    Assert.isTrue(CollectionUtils.isNotEmpty(blackOrWhiteList), "黑名单过滤string不能为空");

                    mapperInfosTobeGenerated = Lists.newArrayList(Collections2.filter(mapperInfos, new Predicate<MapperInfo>() {
                        @Override
                        public boolean apply(MapperInfo input) {

                            for (String black : blackOrWhiteList) {

                                if (input.getClassName().toLowerCase().equals(black.toLowerCase())) {
                                    return false;
                                }
                            }

                            return true;
                        }
                    }));

                    break;
                }
                case 白名单: {
                    Assert.isTrue(CollectionUtils.isNotEmpty(blackOrWhiteList), "白名单过滤string不能为空");
                    mapperInfosTobeGenerated = Lists.newArrayList(Collections2.filter(mapperInfos, new Predicate<MapperInfo>() {
                        @Override
                        public boolean apply(MapperInfo input) {

                            for (String white : blackOrWhiteList) {
                                if (input.getClassName().toLowerCase().equals(white.toLowerCase())) {
                                    return true;
                                }
                            }
                            return false;
                        }
                    }));
                    break;
                }
                default:
                    throw new ComponentException(ComponentException.ErrCode.系统异常, "生成模式异常");
            }

            Assert.notEmpty(mapperInfosTobeGenerated, "未找到待被生成的代码");

            for (MapperInfo mapperInfo : mapperInfosTobeGenerated) {

                generateDao(mapperInfo, config);
                generateDaoImpl(mapperInfo, config);
                generateService(mapperInfo, config);
                generateServiceImpl(mapperInfo, config);
            }

            LOG.info("代码已经全部生成");

            return mapperInfos;


        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void generateFile(String fileName, String content, GenerateConfig config) throws IOException {


        Assert.isTrue(StringUtils.isNotEmpty(fileName), "待生成的文件名不能为空");
        Assert.isTrue(StringUtils.isNotEmpty(content), "待生成的文件名不能为空");
        Assert.notNull(config.getIsCover(), "是否覆盖的信息不能为空");

        File file = new File(fileName);
        String parentDirName = file.getParent();
        File parentDir = new File(parentDirName);
        if (!parentDir.exists()) {
            parentDir.mkdir();
        }

        if (config.getIsCover() == GenerateIsCover.覆盖 || !file.exists()) {
            Files.write(content.getBytes(), file);
            outputGeneratornfo(fileName, GENERATOR_OK);
        } else {
            outputGeneratornfo(fileName, GENERATOR_FILE_EXISTED);
        }

    }


    public static void generateDao(MapperInfo mapperInfo, GenerateConfig config) throws IOException {

        String daoDir = mapperInfo.getDaoBaseDir() + File.separator + (mapperInfo.getMoudleName() == null ? "" : (mapperInfo.getMoudleName() + File.separator)) + mapperInfo.getClassNameCapital() + mapperInfo.getDao() + ".java";

        String daoTemplate = Resources.toString(Resources.getResource("template/dao.template"), Charsets.UTF_8);
        String content = FreeMarkers.renderString(StringUtils.trimToEmpty(daoTemplate), transBean2Map(mapperInfo));
        outputGeneratornfo(daoDir, GENERATOR_ING);

        generateFile(daoDir, content, config);

    }

    public static void outputGeneratornfo(String path, String message) {
        Assert.notNull(path, "路径不能为空");
        Assert.notNull(message, "消息不能为空");
        ArrayList<String> pathStrs = Lists.newArrayList(Splitter.on("/").trimResults().omitEmptyStrings().split(path));

        LOG.info(message + pathStrs.get(pathStrs.size() - 1));
    }


    public static void generateDaoImpl(MapperInfo mapperInfo, GenerateConfig config) throws IOException {
        String daoImplDir = mapperInfo.getDaoBaseDir() + File.separator + (mapperInfo.getMoudleName() == null ? "" : (mapperInfo.getMoudleName() + File.separator)) + mapperInfo.getSubModuleName() + File.separator + mapperInfo.getClassNameCapital() + mapperInfo.getDaoImpl() + ".java";

        String daoImplTemplate = Resources.toString(Resources.getResource("template/daoImpl.template"), Charsets.UTF_8);
        String content = FreeMarkers.renderString(StringUtils.trimToEmpty(daoImplTemplate), transBean2Map(mapperInfo));
        outputGeneratornfo(daoImplDir, GENERATOR_ING);

        generateFile(daoImplDir, content, config);

    }

    public static void generateService(MapperInfo mapperInfo, GenerateConfig config) throws IOException {
        String serviceDir = mapperInfo.getServiceBaseDir() + File.separator + (mapperInfo.getMoudleName() == null ? "" : (mapperInfo.getMoudleName() + File.separator)) + mapperInfo.getClassNameCapital() + mapperInfo.getService() + ".java";

        String serviceTemplate = Resources.toString(Resources.getResource("template/service.template"), Charsets.UTF_8);

        String content = FreeMarkers.renderString(StringUtils.trimToEmpty(serviceTemplate), transBean2Map(mapperInfo));
        outputGeneratornfo(serviceDir, GENERATOR_ING);
        generateFile(serviceDir, content, config);


    }

    public static void generateServiceImpl(MapperInfo mapperInfo, GenerateConfig config) throws IOException {
        String serviceImplDir = mapperInfo.getServiceBaseDir() + File.separator + (mapperInfo.getMoudleName() == null ? "" : (mapperInfo.getMoudleName() + File.separator)) + mapperInfo.getSubModuleName() + File.separator + mapperInfo.getClassNameCapital() + mapperInfo.getServiceImpl() + ".java";

        String serviceTemplate = Resources.toString(Resources.getResource("template/serviceImpl.template"), Charsets.UTF_8);

        String content = FreeMarkers.renderString(StringUtils.trimToEmpty(serviceTemplate), transBean2Map(mapperInfo));

        outputGeneratornfo(serviceImplDir, GENERATOR_ING);
        generateFile(serviceImplDir, content, config);

    }

    /**
     * bean工具方法, 待稳定后单独创建BeanUtils的class
     *
     * @param obj
     * @return
     */
    // Bean --> Map 1: 利用Introspector和PropertyDescriptor 将Bean --> Map
    public static Map<String, String> transBean2Map(Object obj) {

        if (obj == null) {
            LOG.error("传入的对象为空");
            return null;
        }
        Map<String, String> map = new HashMap<String, String>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();

                // 过滤class属性
                if (!key.equals("class")) {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(obj);

                    if (value == null) {
                        map.put(key, null);
                    } else {
                        map.put(key, value.toString());
                    }
                }

            }
        } catch (Exception e) {
            LOG.error("transBean2Map Error " + e);
        }

        return map;

    }

    private static void writeLine(String message) {
        LOG.info(message);
//        System.out.println(message);
    }

    private static void writeLine() {
        System.out.println();
    }

}

